/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.atomojo.app.webdav;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 *
 * @author alex
 */
public class PropfindHandler {

   public static String toString(Date d) {
      DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
      String v = format.format(d);
      return v.substring(0,22)+':'+v.substring(22);
   }
   
   Logger log;
   Map<String,Set<String>> properties;
   int maxDepth = 1;
   public PropfindHandler(Logger log) {
      this.log = log;
      this.properties = new TreeMap<String,Set<String>>();
      this.maxDepth = 1;
   }
   
   public void setRequestDepth(int value)
   {
      maxDepth = value;
   }
   
   public void loadRequest(Reader input)
      throws SAXException,IOException
   {
      XMLReader reader = XMLReaderFactory.createXMLReader();
      reader.setContentHandler(new ContentHandler() {
         int level = -1;
         public void skippedEntity(String name) {}
         public void processingInstruction(String target,String data) {}
         public void ignorableWhitespace(char [] data,int start,int length) {}
         public void characters(char [] data,int start,int length) {}
         public void startElement(String namespaceURI,String localName,String qName,Attributes atts) {
            if (level<0) {
               if ("DAV:".equals(namespaceURI) && "prop".equals(localName)) {
                  level++;
               } 
            } else {
               addPropertyRequest(namespaceURI,localName);
               level++;
            }
         }
         public void endElement(String namespaceURI,String localName,String qName) {
            if (level>=0) {
               level--;
            }
         }
         public void startPrefixMapping(String prefix,String uri) {}
         public void endPrefixMapping(String prefix) {}
         public void startDocument() { }
         public void endDocument() {}
         public void setDocumentLocator(Locator loc) {}
      });
      reader.parse(new InputSource(input));
   }
   
   public void addPropertyRequest(String namespaceURI,String localName) {
      Set<String> set = properties.get(namespaceURI);
      if (set==null) {
         set = new TreeSet<String>();
         properties.put(namespaceURI,set);
      }
      if (!set.contains(localName)) {
         set.add(localName);
      }
   }
   
   static String getSafeString(String value,boolean attribute)
   {
      if (value==null || value.length()==0) {
         return "";
      }
      StringBuilder builder = new StringBuilder();
      char [] chars = value.toCharArray();
      int start = 0,len = 0;
      while ((start+len)<chars.length) {
         switch (chars[start+len]) {
            case '&':
               if (len>0) {
                  builder.append(chars,start,len);
               }
               start += len+1;
               len = 0;
               builder.append("&amp;");
            break;
            case '<':
               if (len>0) {
                  builder.append(chars,start,len);
               }
               start += len+1;
               len = 0;
               builder.append("&lt;");
            break;
            case '\'':
               if (attribute) {
                  if (len>0) {
                     builder.append(chars,start,len);
                  }
                  start += len+1;
                  len = 0;
                  builder.append("&apos;");
               } else {
                  len++;
               }
               break;
            case '"':
               if (attribute) {
                  if (len>0) {
                     builder.append(chars,start,len);
                  }
                  start += len+1;
                  len = 0;
                  builder.append("&quot;");
               } else {
                  len++;
               }
               break;
            default:
               len++;
         }
      }
      if (len!=0) {
         builder.append(chars,start,len);
      }
      return builder.toString();
   }
   
   public void handle(Writer w,DAVResource resource)
      throws IOException
   {
      w.write("<?xml version='1.0' encoding='utf-8' ?>\n<multistatus xmlns='DAV:'>\n");
      handleResource(w,0,resource);
      w.write("</multistatus>\n");
      w.flush();
   }
   protected void handleResource(Writer w,int level,DAVResource resource)
      throws IOException
   {
      w.write("<response>\n");
      w.write("<href>");
      w.write(getSafeString(resource.getReference(),false));
      w.write("</href>\n");
      w.write("<propstat>\n<prop>\n");
      Set<String> unhandled = new TreeSet<String>();

      Set<String> davProperties = properties.get("DAV:");
      for (String prop : davProperties) {
         if (prop.equals("getlastmodified")) {
            w.write("<getlastmodified>");
            w.write(toString(resource.getLastModified()));
            w.write("</getlastmodified>\n");
         } else if (prop.equals("creationdate")) {
            w.write("<creationdate>");
            w.write(toString(resource.getCreated()));
            w.write("</creationdate>\n");
         } else if (prop.equals("resourcetype")) {
            if (resource instanceof DAVCollection) {
               w.write("<resourcetype><collection/></resourcetype>\n");
            } else {
               w.write("<resourcetype/>\n");
            }
         } else if (prop.equals("getcontenttype")) {
            String contentType = resource.getContentType();
            if (contentType!=null) {
               w.write("<getcontenttype>");
               w.write(getSafeString(contentType,false));
               w.write("</getcontenttype>\n");
            } else {
               unhandled.add("getcontenttype");
            }
         } else if (prop.equals("getcontentlength")) {
            long length = resource.getContentLength();
            if (length>=0) {
               w.write("<getcontentlength>");
               w.write(Long.toString(length));
               w.write("</getcontentlength>\n");
            } else {
               unhandled.add("getcontentlength");
            }
         } else if (prop.equals("displayname")) {
            w.write("<displayname>");
            w.write(getSafeString(resource.getDisplayName(),false));
            w.write("</displayname>\n");
         } else {
            log.warning("Unsupported DAV property "+prop);
            unhandled.add(prop);
         }
      }
      w.write("</prop>\n<status>HTTP/1.1 200 OK</status>\n");
      w.write("</propstat>\n");
      if (unhandled.size()>0) {
         w.write("<propstat>\n<prop>\n");
         for (String name : unhandled) {
            w.write("<");
            w.write(name);
            w.write("/>\n");
         }
         w.write("</prop>\n<status>HTTP/1.1 404 Not Found</status>\n");
         w.write("</propstat>\n");
      }
      w.write("</response>\n");
      if ((maxDepth<0 || level<maxDepth) && resource instanceof DAVCollection) {
         int nextLevel = level+1;
         Iterator<DAVResource> children = ((DAVCollection)resource).getChildren();
         while (children!=null && children.hasNext()) {
            DAVResource child = children.next();
            handleResource(w,nextLevel,child);
         }
      }
      
   }
}
